/**
 * File DropTargetPanel.java
 * ---------------------------------------------------------
 *
 * Copyright (C) 2012 David Bauske (david.bauske@googlemail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * - The above copyright notice and this permission notice shall be included in all copies or substantial portions of
 * the Software.
 * - The origin of the software must not be misrepresented.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 * FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * TL;DR: As long as you clearly give me credit for this software, you are free to use as you like, even in commercial
 * software, but don't blame me if it breaks something.
 */
package de.erb.erir.view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JLabel;
import javax.swing.JPanel;

import net.cl.util.patterns.ProgressListener;
import de.erb.erir.model.ConversionProgressEvent;

/**
 * Shows a file icon at the position where the user can drop files.
 *
 * @author David
 */
public class DropTargetPanel extends JPanel implements ProgressListener<ConversionProgressEvent>
{
	private static final long			serialVersionUID			= 4232075232993680572L;

	private static final int			DASHED_STROKE_WIDTH			= 4;
	private static final BasicStroke	DASHED_STROKE				= new BasicStroke(
																			DASHED_STROKE_WIDTH,
																			BasicStroke.CAP_ROUND,
																			BasicStroke.JOIN_ROUND,
																			10.0f,
																			new float[] { 5.0f },
																			0.0f);

	private static final int			FLASH_DURATION				= 500;
	private static final int			FLASH_STEP_DURATION			= 15;
	private static final int			LOADING_BAR_HEIGHT			= 10;
	private static final int			DEFAULT_BODER_SIZE			= 5;

	private int							mBorderSize					= DEFAULT_BODER_SIZE;

	private JLabel						mImage;

	private boolean						mDragHovers					= false;
	private double						mFlashFactor				= 0.0f;

	private Timer						mTimer						= new Timer();
	private FlashTask					mFlashTask;
	private boolean						mFlashing					= false;
	private Color						mCurrentFlashColor			= null;

	private boolean						mShowProgress				= false;
	private double						mFractionDone				= 0;

	private ProgramLook					mProgramLook				= DefaultProgramLook.NEUTRAL;

	private List<DropListener>			mDropListeners				= new ArrayList<DropListener>();

	private class FlashTask extends TimerTask
	{
		boolean	mRising	= true;
		double	mTime	= 0;

		@Override
		public void run()
		{
			mTime += FLASH_STEP_DURATION;
			if(mRising)
			{
				mFlashFactor += ((double) FLASH_STEP_DURATION) / FLASH_DURATION;
				if(mFlashFactor >= 1.0)
				{
					mRising = false;
					mFlashFactor = 1.0;
				}
			}
			else
			{
				mFlashFactor -= ((double) FLASH_STEP_DURATION) / FLASH_DURATION;
				if(mFlashFactor <= 0)
				{
					mFlashFactor = 0;
					cancel();
					mFlashing = false;
					mShowProgress = false;
				}
			}

			DropTargetPanel.this.repaint();
		}
	}

	public DropTargetPanel()
	{
		init();
	}

	/**
	 * Creates a new DropTargetPanel.
	 *
	 * @param pLook The look implementation to use.
	 */
	public DropTargetPanel(ProgramLook pLook)
	{
		mProgramLook = pLook;
		init();
	}

	private void init()
	{
		// Used to center the camera icon
		setLayout(new GridBagLayout());

		mImage = new JLabel(mProgramLook.getDropZoneIcon());
		add(mImage, new GridBagConstraints());

		setDropTarget(new DropTarget(mImage, new DropTargetAdapter()
		{
			@Override
			public void drop(DropTargetDropEvent event)
			{
				boolean successful = false;

				Transferable transferable = event.getTransferable();
				DataFlavor[] flavors = transferable.getTransferDataFlavors();

				for(DataFlavor flavor : flavors)
				{
					if(flavor.isFlavorJavaFileListType())
					{
						// Correct MIME type dropped
						event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
						try
						{
							@SuppressWarnings("unchecked")
							List<File> fileList = (List<File>) event.getTransferable()
									.getTransferData(DataFlavor.javaFileListFlavor);

							for(DropListener dl : mDropListeners)
							{
								dl.onFileListDroppped(fileList);
							}

							event.dropComplete(true);
							successful = true;
						}
						catch(UnsupportedFlavorException e)
						{
							// Cannot happen since the flavor type was checked.
							throw new AssertionError();
						}
						catch(IOException e)
						{
							// Cannot happen since the file type was checked.
							throw new AssertionError();
						}
					}
				}

				if(!successful)
				{
					event.rejectDrop();
				}

				mDragHovers = false;
			}

			@Override
			public void dragEnter(DropTargetDragEvent e)
			{
				mDragHovers = true;

				for(DropListener dl : mDropListeners)
				{
					dl.onDragEntered();
				}
			}

			@Override
			public void dragExit(DropTargetEvent e)
			{
				mDragHovers = false;

				for(DropListener dl : mDropListeners)
				{
					dl.onDragExited();
				}
			}
		}));
	}

	@Override
	protected void paintComponent(Graphics g)
	{
		Graphics2D g2d = (Graphics2D) g;

		super.paintComponent(g);

		// Compute color for the dashed border
		Color paintColor = mDragHovers ? mProgramLook.getHighlightColor()
				: mProgramLook.getForegroundColor();

		if(mFlashing)
		{
			paintColor = new Color(
					(int) (mCurrentFlashColor.getRed() * mFlashFactor + paintColor.getRed()
							* (1.0 - mFlashFactor)),
					(int) (mCurrentFlashColor.getGreen() * mFlashFactor + paintColor.getGreen()
							* (1.0 - mFlashFactor)),
					(int) (mCurrentFlashColor.getBlue() * mFlashFactor + paintColor.getBlue()
							* (1.0 - mFlashFactor))
					);
		}
		g2d.setColor(paintColor);
		g2d.setStroke(DASHED_STROKE);
		g2d.drawRoundRect(mBorderSize, mBorderSize, getWidth() - 2 * mBorderSize, getHeight() - 2
				* mBorderSize, 20, 20);

		// Draw loading bar if necessary
		if(mShowProgress)
		{
			g2d.setColor(mProgramLook.getForegroundColor());
			g2d.fillRect(2 * mBorderSize, getHeight() - 2 * mBorderSize - LOADING_BAR_HEIGHT,
					(int) ((getWidth() - 4 * mBorderSize) * mFractionDone), LOADING_BAR_HEIGHT);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onProgress(ConversionProgressEvent pEvent)
	{
		mFractionDone = pEvent.getPercentage();
		mShowProgress = true;
		repaint();
	}

	/**
	 * Shows the user that the operation succeeded.
	 *
	 * {@inheritDoc}
	 */
	@Override
	public void onOperationDone(ConversionProgressEvent pEvent)
	{
		mFlashFactor = 0.0;
		mFlashTask = new FlashTask();
		mFlashing = true;
		mCurrentFlashColor = mProgramLook.getSuccessColor();
		mTimer.scheduleAtFixedRate(mFlashTask, 0, FLASH_STEP_DURATION);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onOperationCancelled(ConversionProgressEvent pEvent)
	{
		mShowProgress = false;
		mFlashFactor = 0.0;
		mFlashTask = new FlashTask();
		mFlashing = true;
		mCurrentFlashColor = mProgramLook.getErrorColor();
		mTimer.scheduleAtFixedRate(mFlashTask, 0, FLASH_STEP_DURATION);
	}

	public void addDropListener(DropListener pListener)
	{
		if(pListener == null)
		{
			throw new NullPointerException("pListener may not be null");
		}

		mDropListeners.add(pListener);
	}

	public void removeDropListener(DropListener pListener)
	{
		mDropListeners.remove(pListener);
	}
}
